<?php
require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'wsr.inc.php';
require_once MORIARTY_ARC_DIR . 'ARC2.php';

class RuleRunner {
	var $ns;
	var $index;
	var $triples;
	var $inferred_triples;
	function __construct( $graph=false ) {
		if ( $graph ) $this->setGraph($graph);
		$this->inferred_triples = array();
	}
	function setGraph($graph) {
		if ( $graph && is_object($graph) && is_a($graph,'SimpleGraph') ){
			$this->index = $graph->_index;
			$this->ns = $graph->_ns;
		} elseif ( $graph && is_array($graph) ){
			$this->index = $graph;
		} else {
			die("bad argument to RuleRunner");
		}
		$this->triples = ARC2::getTriplesFromIndex($this->index);
	}
	function clear_inferred_triples() {
		$this->inferred_triples = array();
	}
	function add_inferred_triples($triples) {
		$this->inferred_triples = array_merge($this->inferred_triples,$triples);
	}
	function runRule($rule) {
	}
	function runRuleset($ruleset) {
		$rules = $ruleset->getRules();
		foreach( $rules as $arule ) {
			$this->runOne( $arule['lhs'], $arule['rhs'] );
		}
	}
	function runOne( $lhs, $rhs ) {
		$bindings = $this->match_lhs( $lhs );
		foreach( $bindings as $bound ) {
			$new_triples = $this->substitute_rhs( $rhs, $bound );
			$this->add_inferred_triples($new_triples);
		}
	}
	function match_lhs( $lhs ) {
		$bindings = array( array() );
		foreach( $lhs as $pat ) {
			$newbindings = array();
			foreach( $bindings as $binding ) {
				$matchbindings = $this->match( $pat, $binding );
				$newbindings = array_merge($newbindings,$matchbindings);
			}
			$bindings = $newbindings;
		}
		return $bindings;
	}
	function match( $pat, $bound ) {
		//echo "match( pat:" . print_r($pat,true) . ", bound:" . print_r($bound,true) . " )\n";
		$bindings = array();
		foreach( $this->triples as $triple ) {
			//echo "MATCH: " . print_r($triple,true) . "\n";
			$s_bound = $this->match_one( $pat['s'], $pat['s_type'], $triple['s'], $triple['s_type'], $bound );
			if ( $s_bound === false ) continue;
			//echo "matched\n";
			$p_bound = $this->match_one( $pat['p'], $pat['p_type'], $triple['p'], 'uri', $s_bound );
			if ( $p_bound === false ) continue;
			$o_bound = $this->match_one( $pat['o'], $pat['o_type'], $triple['o'], $triple['o_type'], $p_bound );
			if ( $o_bound === false ) continue;
			$bindings[] = $o_bound;
		}
		return $bindings;
	}
	function match_one( $pat_val, $pat_type, $t_val, $t_type, $bound ) {
		//echo "match_one( $pat_val, $pat_type, $t_val, $t_type, " . print_r($bound,true) . " )\n";
		if ( $pat_type == 'var' ) {
			if ( array_key_exists($pat_val,$bound) ) {
				if ( $bound[$pat_val]['type'] === $t_type && $bound[$pat_val]['value'] === $t_val ) {
					return $bound;
				} else {
					return false;
				}
			} else {
				$bound[$pat_val] = array( 'type' => $t_type, 'value' => $t_val );
				return $bound;
			}
		} else if ( $pat_type === $t_type && $pat_val === $t_val ) {
			return $bound;
		} else {
			return false;
		}
	}
	function substitute_rhs( $rhs, $bound ) {
		$tuples = array();
		foreach( $rhs as $pat ) {
			list( $s, $s_type ) = $this->substitute_one( $pat['s'], $pat['s_type'], $bound );
			list( $p, $p_type ) = $this->substitute_one( $pat['p'], $pat['p_type'], $bound );
			list( $o, $o_type ) = $this->substitute_one( $pat['o'], $pat['o_type'], $bound );
			$tuples[] = array( 's' => $s, 'p' => $p, 'o' => $o, 's_type' => $s_type, 'p_type' => $p_type, 'o_type' => $o_type );
		}
		return $tuples;
	}
	function substitute_one( $val, $type, $bound ) {
		if ( $type == 'var' ) {
			if ( array_key_exists($val,$bound) ) {
				return array( $bound[$val]['value'], $bound[$val]['type'] );
			} else {
				die("missing wildcard");  // should do someting more sensible!!
			}
		} else {
			return array($val, $type);
		}
	}
	function bindings_sort($bindings,$wildcards) {
		$comparator = new BindingComparator($wildcards);
		return usort($bindings,array($comparator,'compare'));
	}
}

class BindingComparator {
	var $wildcards;
	function __construct( $wildcards ) {
		$this->wildcards = $wildcards;
	}
	function compare($b1,$b2) {
		foreach ( $wildcards as $wc ) {
			if ( array_key_exists($wc,$b1) ) {
				if ( array_key_exists($wc,$b2) ) {
					// both defined, compare values
					if (  ( $res = strcmp($b1['type'],$b2['type']) ) != 0 ) return $res;
					if (  ( $res = strcmp($b1['value'],$b2['value']) ) != 0 ) return $res;
					// identical fall through to compare next
				} else {
					// just b1 defined
					return 1;
				}
			} else {
				if ( array_key_exists($wc,$b2) ) {
					// just b2 defined
					return -1;
				} else {
					// neither exists, fall through to compare next
				}
			}
		}
		// all equal
		return 0;
	}
}

class Rule {
	var $lhs;
	var $rhs;
	var $ns;
	var $ns_turtle;
	var $lhs_parsed;
	var $rhs_parsed;
	var $lhs_wildcards;
	var $rhs_wildcards;
	var $error;
	function __construct( $lhs, $rhs, $opts ) {
		if ( is_string( $lhs ) ) $lhs = array( $lhs );
		if ( is_string( $rhs ) ) $rhs = array( $rhs );
		$this->lhs = $lhs;
		$this->rhs = $rhs;
		$this->opts = $opts;
		$this->ns = $opts['namespaces'];
		$this->ns_turtle = $this->ns_to_turtle($this->ns);
		$this->lhs_parsed = $this->parsePatterns( $this->lhs );
		$this->rhs_parsed = $this->parsePatterns( $this->rhs );
		$this->lhs_wildcards = $this->extractWildcards( $this->lhs_parsed );
		$this->rhs_wildcards = $this->extractWildcards( $this->rhs_parsed );
		$undefined_wc = array_diff( $this->rhs_wildcards, $this->lhs_wildcards );
		if ( count($undefined_wc) > 0 ) {
			$errors[] = "undefined variables on rhs: " . implode(", ",$undefined_wc);
		}
	}
	function run($graph) {
	}
	function ns_to_turtle($ns) {
		$turtle = "";
		foreach( $ns as $prefix => $url ) {
			$turtle .= "@prefix ".$prefix.": <".$url.">.\n";
		}
		return $turtle;
	}
	/*
	 * @returns array of wildcards
	 */
	function extractWildcards( $triples, $returninkeys = false ) {
		$wildcards = array();
		foreach( $triples as $triple ) {
			if ( $triple['s_type'] == 'var' ) $wildcards[$triple['s']] = true;
			if ( $triple['p_type'] == 'var' ) $wildcards[$triple['p']] = true;
			if ( $triple['o_type'] == 'var' ) $wildcards[$triple['o']] = true;
		}
		if ( $returninkeys ) return $wildcards;
		else                 return array_keys($wildcards);
	}
	/*
	 * @returns triple array
	 */
	function parsePatterns( $patarr ) {
		$pat_turtle = implode( " .\n", $patarr );
		$parser = ARC2::getTurtleParser();
		$data = $this->ns_turtle . "\n\n" . $pat_turtle . ".\n";
		//echo"==== DATA ====\n\n";
		//echo $data;
		//echo"==============\n\n";
		$base = 'http://example.com/';
		$parser->parse($base,$data);
		$triples = $parser->getTriples();
		$errors = $parser->getErrors();
		$warnings = $parser->getWarnings();
		return $triples;
	}
}

?>